# coding=utf-8
from base64 import b64encode
from collections import OrderedDict
from urllib.parse import urlencode

from django.db.models import QuerySet
from rest_framework.pagination import CursorPagination as OriginCurorPagination
from rest_framework.response import Response


class CursorPagination(OriginCurorPagination):
    cursor_query_param = 'next'
    ordering = '-created_at'
    max_page_size = 20
    page_size = 20
    page_size_query_param = "limit"
    count = None

    def get_count(self, queryset, **kwargs):
        return queryset.count()

    def paginate_queryset(self, queryset, request, view=None):
        if view is not None and getattr(view, "show_count", False):
            self.count = self.get_count(queryset)
        if isinstance(queryset, QuerySet):
            return super(CursorPagination, self).paginate_queryset(queryset, request, view)
        return None

    def get_paginated_response(self, data):
        data = [
            ('next', self.get_next_link()),
            ('list', data)
        ]
        if self.count is not None:
            data.append(('count', self.count))
        return Response(OrderedDict(data))

    def get_ordering(self, request, queryset, view):
        """
        Return a tuple of strings, that may be used in an `order_by` method.
        """
        ordering_filters = [
            filter_cls for filter_cls in getattr(view, 'filter_backends', [])
            if hasattr(filter_cls, 'get_ordering')
        ]

        if ordering_filters:
            # If a filter exists on the view that implements `get_ordering`
            # then we defer to that filter to determine the ordering.
            filter_cls = ordering_filters[0]
            filter_instance = filter_cls()
            ordering = filter_instance.get_ordering(request, queryset, view)
            assert ordering is not None, (
                'Using cursor pagination, but filter class {filter_cls} '
                'returned a `None` ordering.'.format(
                    filter_cls=filter_cls.__name__
                )
            )
        else:
            # The default case is to check for an `ordering` attribute
            # on this pagination instance.
            ordering = getattr(view, "pagination_ordering", self.ordering)
            assert ordering is not None, (
                'Using cursor pagination, but no ordering attribute was declared '
                'on the pagination class.'
            )

        assert isinstance(ordering, (str, list, tuple)), (
            'Invalid ordering. Expected string or tuple, but got {type}'.format(
                type=type(ordering).__name__
            )
        )

        if isinstance(ordering, str):
            return (ordering,)
        return tuple(ordering)

    def encode_cursor(self, cursor):
        """
        Given a Cursor instance, return an url with encoded cursor.
        """
        tokens = {}
        if cursor.offset != 0:
            tokens['o'] = str(cursor.offset)
        if cursor.reverse:
            tokens['r'] = '1'
        if cursor.position is not None:
            tokens['p'] = cursor.position

        querystring = urlencode(tokens, doseq=True)
        encoded = b64encode(querystring.encode('ascii')).decode('ascii')
        return encoded
